use axum::{
    async_trait,
    extract::{Form, FromRequest, Query, RequestParts},
    http::StatusCode,
    response::{IntoResponse, Response},
    BoxError, Json,
};
use serde::de::DeserializeOwned;
use thiserror::Error;
use validator::Validate;

use crate::responses::CusResponse;

#[derive(Debug, Clone, Copy, Default)]
pub struct ValidatedForm<T>(pub T);

#[async_trait]
impl<T, B> FromRequest<B> for ValidatedForm<T>
    where
        T: DeserializeOwned + Validate,
        B: http_body::Body + Send,
        B::Data: Send,
        B::Error: Into<BoxError>,
{
    // type Rejection = ServerError;
    type Rejection = super::AppError;

    async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
        let Form(value) = Form::<T>::from_request(req).await?;
        value.validate()?;
        Ok(ValidatedForm(value))
    }
}

#[derive(Debug, Clone, Copy, Default)]
pub struct ValidatedJson<T>(pub T);

#[async_trait]
impl<T, B> FromRequest<B> for ValidatedJson<T>
    where
        T: DeserializeOwned + Validate,
        B: http_body::Body + Send,
        B::Data: Send,
        B::Error: Into<BoxError>,
{
    // type Rejection = ServerError;
    type Rejection = super::AppError;

    async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
        let Json(value) = Json::<T>::from_request(req).await?;
        value.validate()?;
        Ok(ValidatedJson(value))
    }
}

#[derive(Debug, Clone, Copy, Default)]
pub struct ValidatedQuery<T>(pub T);

#[async_trait]
impl<T, B> FromRequest<B> for ValidatedQuery<T>
    where
        T: DeserializeOwned + Validate,
        B: http_body::Body + Send,
        B::Data: Send,
        B::Error: Into<BoxError>,
{
    // type Rejection = ServerError;
    type Rejection = super::AppError;

    async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
        let Query(value) = Query::<T>::from_request(req).await?;
        value.validate()?;
        Ok(ValidatedQuery(value))
    }
}

// #[derive(Debug, Error)]
// pub enum ServerError {
//     #[error(transparent)]
//     ValidationError(#[from] validator::ValidationErrors),
//
//     #[error(transparent)]
//     AxumFormRejection(#[from] axum::extract::rejection::FormRejection),
//
//     #[error(transparent)]
//     AxumJsonRejection(#[from] axum::extract::rejection::JsonRejection),
//
//     #[error(transparent)]
//     AxumQueryRejection(#[from] axum::extract::rejection::QueryRejection),
//
//     #[error(transparent)]
//     Err(anyhow::Error),
// }
//
// impl IntoResponse for ServerError {
//     fn into_response(self) -> Response {
//         tracing::error!(
//             "{}",
//             format!("server error: [{:?}]", self).replace('\n', ", ")
//         );
//         match self {
//             ServerError::ValidationError(rejection) => {
//                 let message = format!("Validation error: [{}]", rejection).replace('\n', ", ");
//                 (StatusCode::OK, CusResponse::error(message))
//             }
//             ServerError::AxumFormRejection(rejection) => {
//                 let message = format!("Form validation error: [{}]", rejection).replace('\n', ", ");
//                 (StatusCode::OK, CusResponse::error(message))
//             }
//             ServerError::AxumJsonRejection(rejection) => {
//                 let message = format!("Json validation error: [{}]", rejection).replace('\n', ", ");
//                 (StatusCode::OK, CusResponse::error(message))
//             }
//             ServerError::AxumQueryRejection(rejection) => {
//                 let message = format!("Query validation error: [{}]", rejection).replace('\n', ", ");
//                 (StatusCode::OK, CusResponse::error(message))
//             }
//             ServerError::Err(rejection) => {
//                 let message = format!("server error: [{}]", rejection).replace('\n', ", ");
//                 (
//                     StatusCode::INTERNAL_SERVER_ERROR,
//                     CusResponse::<String>::error(message),
//                 )
//             }
//         }
//             .into_response()
//     }
// }
